﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace VsTortoiseSVN.Dialogs
{
    public partial class OpenModifiedDocumentDialog : Form, IMessageFilter
    {
        private DTE2 _dte { get; set; }
        private SvnVsProjectStatus _vsStatusProject { get; set; }
        private SvnHelperStatusItem[] _statusItems { get; set; }
        private Window _lastOpenedWindow { get; set; }
        private ImageList _smallFileIcons { get; set; }

        private SvnHelperStatusItem[] _selection
        {
            get
            {
                SvnHelperStatusItem[] items = new SvnHelperStatusItem[listViewDocs.SelectedItems.Count];

                for (int n = 0; n < listViewDocs.SelectedItems.Count; ++n)
                {
                    items[n] = listViewDocs.SelectedItems[n].Tag as SvnHelperStatusItem;
                }

                return items;
            }
        }

        private string[] _selectedFilenames
        {
            get
            {
                SvnHelperStatusItem[] selection = this._selection;
                string[] filenames = new string[selection.Length];

                for (int n = 0; n < selection.Length; ++n)
                {
                    filenames[n] = selection[n].Status.FullPath;
                }

                return filenames;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public OpenModifiedDocumentDialog(DTE2 dte)
            : base()
        {
            InitializeComponent();           

            _dte = dte;

            _smallFileIcons = new ImageList();
            _smallFileIcons.ColorDepth = ColorDepth.Depth32Bit;
            _smallFileIcons.ImageSize = new Size(16, 16);
            listViewDocs.SmallImageList = _smallFileIcons;

            SetSingleSelectionContextMenuImages();
            SetMultiSelectionContextMenuImages();
        }

        /// <summary>
        /// Loads bitmaps from the satellite DLL and assigns them to the "single selection" context menu.
        /// </summary>
        private void SetSingleSelectionContextMenuImages()
        {
            itemDiffToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Diff);
            itemRevertToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Revert);
            itemShowLogToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.ShowLog);
            itemExploreToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.ExploreTo);
            itemBlameToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Blame);
            itemUpdateToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Update);
            itemCommitToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Commit);
            itemPropertiesToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Properties);
        }

        /// <summary>
        /// Loads bitmaps from the satellite DLL and assigns them to the "multi selection" context menu.
        /// </summary>
        private void SetMultiSelectionContextMenuImages()
        {
            itemsDiffToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Diff);
            itemsRevertToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Revert);
            itemsUpdateToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Update);
            itemsCommitToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Commit);
            itemsPropertiesToolStripMenuItem.Image = SatelliteDLL.Instance.GetTransparentBitmap((int)BitmapResourceId.Properties);
        }

        private void WorkCompletedMethod(object sender, RunWorkerCompletedEventArgs e)
        {
            UpdateListView();
        }


        /// <summary>
        /// Used to forward KeyDown / KeyUp Messages send to
        /// the filter TextBox Control to the ListView as well. This
        /// allows for example to navigate in the ListView with the Cursor keys
        /// while being actually in the TextBox.
        /// </summary>
        public bool PreFilterMessage(ref Message msg)
        {
            if (ActiveControl == textBoxFilter)
            {
                switch(msg.Msg)
                {
                    case Win32.WM_KEYUP:
                    case Win32.WM_KEYDOWN:
                        if ((Keys)msg.WParam == Keys.Up || (Keys)msg.WParam == Keys.Down)
                        {
                            // Only catch cursor up/down keys
                            Win32.SendMessage(listViewDocs.Handle, msg.Msg, msg.WParam, msg.LParam);
                            return true;
                        }
                        break;

                    default:
                        break;
                }
            }

            switch (msg.Msg)
            {
                case Win32.WM_KEYDOWN:
                    if ((Keys)msg.WParam == Keys.F5 && !_vsStatusProject.IsBusy)
                    {
                        _vsStatusProject.Start();
                        return true;
                    }
                    break;
            }

            return false;
        }

        private void OpenModifiedDocumentDialog_Shown(object sender, EventArgs e)
        {
            Application.AddMessageFilter(this);
            ActiveControl = textBoxFilter;
            textBoxFilter.SelectAll();
        }

        private void OpenModifiedDocumentDialog_FormClosed(object sender, FormClosedEventArgs e)
        {
            _vsStatusProject.Abort();

            Application.RemoveMessageFilter(this);
        }

        private void textBoxFilter_TextUpdate(object sender, EventArgs e)
        {
            if (_vsStatusProject.IsBusy)
                return;

            UpdateListView();
        }

        public void Scan(ICollection<string> pathes)
        {
            Text = SatelliteDLL.Instance.GetText("OpenModifiedFileRetrievingFileStatusInformation");
            buttonOk.Enabled = false;
            listViewDocs.Items.Clear();
            listViewDocs.ContextMenuStrip = null;

            // We are interested in svn-modified and added files only
            SvnHelperStatusBools statusBools = SvnHelperStatusBools.CreateEmpty();
            statusBools.Modified = true;
            statusBools.Added = true;

            // Create and start the new project status thread helper
            _vsStatusProject = new SvnVsProjectStatus(pathes, statusBools);
            _vsStatusProject.RunWorkerCompleted += WorkCompletedMethod;
            _vsStatusProject.Start();
        }

        private void UpdateListView()
        {
            if (listViewDocs.InvokeRequired)
            {
                listViewDocs.Invoke(new MethodInvoker(UpdateListView));
                return;
            }

            bool error;
            _statusItems = _vsStatusProject.Filter(textBoxFilter.Text, out error);
            if (_statusItems == null)
            {
                buttonOk.Enabled = false;
                return;
            }

            listViewDocs.BeginUpdate();
            try
            {
                listViewDocs.Items.Clear();

                for (int n = 0; n < _statusItems.Length; ++n)
                {
                    SvnHelperStatusItem statusItem = _statusItems[n];

                    // Only add files to the list. Without this check the list
                    // would display folders as well, which is confusing because
                    // those cannot be opened in Visual Studio.
                    if(statusItem.Status.NodeKind == SharpSvn.SvnNodeKind.File)
                    {
                        if (!_smallFileIcons.Images.ContainsKey(statusItem.Info.Extension))
                        {
                            Icon smallIcon = Win32.GetSmallIcon(statusItem.Info.FullName);
                            _smallFileIcons.Images.Add(statusItem.Info.Extension, smallIcon);
                        }

                        ListViewItem item = new ListViewItem(statusItem.Info.Name);
                        item.ImageKey = statusItem.Info.Extension;
                        item.Tag = statusItem;
                        item.SubItems.Add(statusItem.Info.FullName);
                        item.SubItems.Add(statusItem.Status.LocalContentStatus.ToString());
                        item.SubItems.Add(statusItem.Info.LastWriteTime.ToShortDateString() + " " + statusItem.Info.LastWriteTime.ToShortTimeString());

                        listViewDocs.Items.Add(item);
                    }
                }
            }
            finally
            {
                listViewDocs.EndUpdate();
            }

            textBoxFilter.BackColor = error ? Color.IndianRed : SystemColors.Window;
            textBoxFilter.ForeColor = error ? Color.White : SystemColors.WindowText;

            buttonOk.Enabled = false;
            Text = string.Format(SatelliteDLL.Instance.GetText("OpenModifiedFileItemFilterCount"), _statusItems.Length, _vsStatusProject.Count);
        }

        private void buttonOk_Click(object sender, EventArgs e)
        {
            _lastOpenedWindow = null;

            SvnHelperStatusItem[] selection = _selection;
            foreach (var statusItem in selection)
            {
                try
                {
                    _lastOpenedWindow = _dte.ItemOperations.OpenFile(statusItem.Info.FullName, Constants.vsViewKindPrimary);
                }
                catch //(System.Exception ex)
                { }
            }

            DialogResult = DialogResult.OK;
            Close();

            closeTimer.Enabled = true;
        }

        /// <summary>
        /// This method is called when the list selection changed.
        /// It's used to assign the correct context menu to the list control,
        /// which depends on whether multiple items are selected.
        /// </summary>
        private void listViewDocs_SelectedIndexChanged(object sender, EventArgs e)
        {
            int selCount = listViewDocs.SelectedItems.Count;
            switch (selCount)
            {
                case 0:
                    // No item is selected. Don't display a context-menu for that.
                    listViewDocs.ContextMenuStrip = null;
                    break;

                case 1:
                    // One item is selected.
                    listViewDocs.ContextMenuStrip = singleSelectionContextMenu;
                    break;

                default:
                    // More than one item is selected
                    listViewDocs.ContextMenuStrip = multiSelectionContextMenu;
                    break;
            }

            // Enable the OK button only when
            // at least one item is selected.
            buttonOk.Enabled = selCount > 0;
        }

        private void closeTimer_Tick(object sender, EventArgs e)
        {
            Timer timer = sender as Timer;
            timer.Enabled = false;

            if (_lastOpenedWindow != null)
                _lastOpenedWindow.Activate();
        }

        private void diffToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bool compareItems = true;
            string[] selection = _selectedFilenames;

            if (selection.Length > 10)
            {
                // It's unlikely the user wants to open so many diff instances
                // that we better show a confirmation dialog.
                compareItems = System.Windows.Forms.MessageBox.Show(
                    string.Format(SatelliteDLL.Instance.GetText("OpenModifiedFileCompareQuestion"), selection.Length),
                    "VsTortoise",
                    MessageBoxButtons.YesNo) == DialogResult.Yes;
            }

            if(compareItems)
                TortoiseProc.CompareWithBase(selection);
        }

        private void unifiedDiffStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Diff(selection);
        }

        private void showLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.ShowLog(selection[0]);
        }

        private void blameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Blame(selection[0]);
        }

        private void revertToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Revert(selection);
        }

        private void updateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Update(selection);
        }

        private void commitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Commit(selection);
        }

        private void exploreToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Win32.ExploreTo(_selection[0].Status.FullPath);
        }

        private void propertiesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string[] selection = _selectedFilenames;
            TortoiseProc.Properties(selection);
        }

        private void SelectAll()
        {
            ListView.ListViewItemCollection items = listViewDocs.Items;
            foreach (ListViewItem item in items)
            {
                item.Selected = true;
            }
        }

        private void listViewDocs_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == (Keys.Control | Keys.A))
            {
                SelectAll();
                e.Handled = true;
            }
        }
    }

    
    public class SvnVsProjectStatus
    {
        private List<SvnHelperStatusItem> _items { get; set; }
        private List<string> _folders { get; set; }
        private SvnHelperStatusBools _statusBools { get; set; }
        private BackgroundWorker _worker = new BackgroundWorker();

        public event ProgressChangedEventHandler ProgressChanged
        {
            add { _worker.ProgressChanged += value; }
            remove { _worker.ProgressChanged -= value; }
        }

        public event RunWorkerCompletedEventHandler RunWorkerCompleted
        {
            add { _worker.RunWorkerCompleted += value; }
            remove { _worker.RunWorkerCompleted -= value; }
        }

        public SvnVsProjectStatus(ICollection<string> folders, SvnHelperStatusBools statusBools)
        {
            _folders = new List<string>(folders);
            PathHelper.RemoveSubDirectories(_folders);

            _statusBools = statusBools;
            
            _items = new List<SvnHelperStatusItem>(512);

            _worker.WorkerReportsProgress = true;
            _worker.WorkerSupportsCancellation = true;
            _worker.DoWork += new DoWorkEventHandler(WorkMethod);
        }

        public bool IsBusy
        {
            get
            {
                return _worker.IsBusy;
            }
        }

        public int Count
        {
            get
            {
                return _items.Count;
            }
        }

        public void Start()
        {
            if (IsBusy)
                _worker.CancelAsync();

            _worker.RunWorkerAsync();
        }

        public void Abort()
        {
            if (_worker.IsBusy)
                _worker.CancelAsync();
        }

        public SvnHelperStatusItem[] Filter(string pattern, RegexOptions options, out bool error)
        {
            error = false;

            lock (_items)
            {
                // When no filter pattern has been specified
                // then display the entire list.
                if (string.IsNullOrEmpty(pattern))
                {
                    return _items.ToArray();
                }

                // Filter pattern has been specified.
                // Now check for any matches and only add items on a match.
                List<SvnHelperStatusItem> filtered = new List<SvnHelperStatusItem>(_items.Count);

                try
                {
                    Regex regex = new Regex(pattern, options);
                    foreach (SvnHelperStatusItem item in _items)
                    {
                        if (regex.IsMatch(item.Info.Name))
                            filtered.Add(item);
                    }
                }
                catch
                {
                    // If an exception occurs, return at least
                    // the items found till then.
                    error = true;
                    return filtered.ToArray();
                }

                return filtered.ToArray();
            }
        }

        public SvnHelperStatusItem[] Filter(string pattern, out bool error)
        {
            return Filter(pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.IgnoreCase, out error);
        }

        private void WorkMethod(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker myBw = sender as BackgroundWorker;

            lock (_items)
            {
                _items.Clear();

                // Get the SVN status for each folder
                foreach (string folder in _folders)
                {
                    if (myBw.CancellationPending)
                    {
                        e.Cancel = true;
                        break;
                    }

                    SvnHelper.Status(_items, folder, _statusBools);
                }
            }

            e.Result = true;
        }
    }
}
